package pl.agh.pp.zephyr.model;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import pl.agh.pp.zephyr.integration.GkController;
import pl.agh.pp.zephyr.integration.McuController;
import pl.agh.pp.zephyr.utils.LogPublisher;

/**
 * Główna klasa modelu sieci.
 * 
 * Należy wykorzystywać jej metody do manipulowania modelem.
 * */
public class H323Area extends LogPublisher implements Serializable {

	private static final long serialVersionUID = 1L;

	public H323Area() {
		super(H323Area.class);
	}

	public final Set<Gatekeeper> gks = Collections
			.synchronizedSet(new HashSet<Gatekeeper>());

	public final Set<Mcu> mcus = Collections
			.synchronizedSet(new HashSet<Mcu>());

	public final Set<User> users = Collections
			.synchronizedSet(new HashSet<User>());

	public final Set<Room> rooms = Collections
			.synchronizedSet(new HashSet<Room>());

	private final Map<Integer, Object> all = Collections
			.synchronizedMap(new HashMap<Integer, Object>());

	private long lastModified = 0;

	/**
	 * unsafe
	 * */
	public <T> T getElement(int hash) {
		T t;
		for (Collection<?> c : new Collection<?>[] { gks, mcus, rooms, users }) {
			t = (T)get(c, hash);
			if (t != null) {
				return t;
			}
		}
		return null;
	}

	public <T> T getElement(int hash, Class<T> c) {
		T t = getElement(hash);
		return (c.isInstance(t)) ? t : null;
	}

	private <T> T get(Collection<T> cc, int hash) {
		for (T c : cc) {
			if (c != null && c.hashCode() == hash)
				return c;
		}
		return null;
	}

	public boolean removeElement(String host, String port) {
		return removeElement(new Host(host, port).hashCode());
	}
	
	public boolean removeElement(int hash) {
		touch();
		Object o = all.remove(hash);
		if (o == null) {
			return false;
		}
		if (o instanceof Gatekeeper) {
			Gatekeeper g = (Gatekeeper)o;
			for (User u : g.users) {
				removeElement(u.hashCode());
			}
			return gks.remove(o);
		}
		if (o instanceof Mcu) {
			Mcu m = (Mcu)o;
			for (Room r : m.rooms) {
				removeElement(r.hashCode());
			}
			return mcus.remove(o);
		}
		if (o instanceof Room) {
			Room r = (Room)o;
			for (Room second : r.connections) {
				second.connections.remove(r);
			}
			for (User u : r.members) {
				disconnectUserFromRoom(u.hashCode());
			}
			return rooms.remove(o);
		}
		if (o instanceof User) {
			User u = (User)o;
			disconnectUserFromRoom(u.hashCode());
			u.gk.users.remove(u);
			return users.remove(o);
		}
		return false;
	}

	public boolean connectUserToRoom(int userHash, int roomHash) {
		touch();
		Room r = get(rooms, roomHash);
		User u = get(users, userHash);
		if (r == null || u == null || u.getRoom() != null) {
			return false;
		}
		r.members.add(u);
		u.setRoom(r);
		return true;
	}

	public boolean disconnectUserFromRoom(int userHash) {
		touch();
		User u = get(users, userHash);
		if (u == null || u.getRoom() == null) {
			return false;
		}
		u.getRoom().members.remove(u);
		// TODO czy jak pokoj pusty to go wywalić z modelu?
		u.setRoom(null);
		return true;
	}

	public Integer registerUser(String ip, String port, String alias, int gatekeeperHash) {
		touch();
		log.info("registerUser: " + ip + ":" + port);
		Gatekeeper g = get(gks, gatekeeperHash);
		if (g == null) {
			return null;
		}
		User u = new User(ip, port, g);
		u.setAlias(alias);
		g.users.add(u);
		return safeAdd(users, u);
	}
	
	public void kurwa() {}

	public Integer registerGatekeeper(String ip, String port,
			GkController controller) {
		touch();
		return safeAdd(gks, new Gatekeeper(ip, port, controller));
	}

	public Integer registerMcu(String ip, String port, String prefix, String httpPort, McuController controller) {
		touch();
		return safeAdd(mcus, new Mcu(ip, port, prefix, httpPort, controller));
	}

	public Integer createRoom(String alias, int initialMcuHash) {
		touch();
		Mcu m = get(mcus, initialMcuHash);
		if (m == null) {
			return null;
		}
		Room r = new Room(alias, m);
		m.rooms.add(r);
		return safeAdd(rooms, r);
	}

	private <T> Integer safeAdd(Set<T> set, T object) {
		if (set == null || object == null) {
			return null;
		}
		final boolean b = set.add(object);
		if (b) {
			all.put(object.hashCode(), object);
		}
		return object.hashCode();
	}
	
	public Room getRoom(String alias, int mcuHash) {
		for (Room r : rooms) {
			if (r.alias.equals(alias) && r.mcu.hashCode() == mcuHash) {
				return r;
			}
		}
		return null;
	}
	
	private void touch() {
		lastModified = System.currentTimeMillis();
	}

	public boolean hasModelChangedSince(long currentTimeMillis) {
		return currentTimeMillis < lastModified  ;
	}

}
